#pragma once
#include <interface.h>

#include <map>
#include <vector>
#include <memory>
#include <algorithm>

class VertexArray {
  public:
    VertexArray() {
      glGenVertexArrays(1, &vao_);
      glGenBuffers(1, &vbo_);
      glGenBuffers(1, &ebo_);
      glBindVertexArray(vao_);
      glBindBuffer(GL_ARRAY_BUFFER, vbo_);
      glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo_);
      glBindVertexArray(0);
    }
    ~VertexArray() {
      glBindVertexArray(0);
      glDeleteVertexArrays(1, &vao_);
      glDeleteBuffers(1, &vbo_);
      glDeleteBuffers(1, &ebo_);
    }
    inline void setLayout(GLuint index, GLint size, GLenum type, unsigned long long typesize, GLsizei stride, GLsizei offset) {
      glVertexAttribPointer(index, size, type, GL_FALSE, stride * typesize, (void*)(offset * typesize));
      glEnableVertexAttribArray(index);
    }
    inline void use() { glBindVertexArray(vao_); }
  protected:
    GLuint vao_, vbo_, ebo_;
};

struct sDrawData {
  VertexArray *vao;
  Shader *shader;
  Texture *texture;
  std::vector<float> vertices; 
  std::vector<GLuint> indices;
  GLuint count;
};

struct sRender {
  std::map<std::string, std::shared_ptr<VertexArray>> vaos;
  std::map<std::string, std::shared_ptr<Shader>> shaders;
  std::map<std::string, std::shared_ptr<Texture>> textures;
  std::vector<std::shared_ptr<sDrawData>> draws;
  void free() {
    vaos.clear();
    shaders.clear();
    textures.clear();
    draws.clear();
  }
  std::shared_ptr<VertexArray> loadVertexArray(const GLchar *name) {
    if (!name) return nullptr;
    auto vao = std::make_shared<VertexArray>();
    if (!vao) return nullptr;
    vaos[name] = vao;
    return vao;
  }
  std::shared_ptr<Shader> loadShader(const GLchar *vertextPath, const GLchar *fragmentPath, const GLchar *name = 0) {
    auto s = std::make_shared<Shader>();
    if (!s) return nullptr;
    bool ok = s->loadPath(vertextPath, fragmentPath);
    if (!ok) return nullptr;
    if (!name) name = vertextPath;
    shaders[name] = s;
    return s;
  }
  std::shared_ptr<Texture> loadTexture(const GLchar *path, const GLchar *name = 0) {
    auto t = std::make_shared<Texture>();
    if (!t) return nullptr;
    bool ok = t->loadPath(path);
    if (!ok) return nullptr;
    if (!name) name = path;
    textures[name] = t;
    return t;
  }
  void flush() {
    for (auto &e : draws) {
      e->vao->use();
      glBufferData(GL_ARRAY_BUFFER, e->vertices.size(), e->vertices.data(), GL_STATIC_DRAW);
      glBufferData(GL_ELEMENT_ARRAY_BUFFER, e->indices.size(), e->indices.data(), GL_STATIC_DRAW);
      e->vertices.clear();
      e->indices.clear();
    }
  }
  void render() {
    for (auto &e : draws) {
      e->vao->use();
      e->shader->use();
      e->texture->use(GL_TEXTURE0);
      glDrawElements(GL_TRIANGLES, e->count, GL_UNSIGNED_INT, nullptr);
    }
  }
  std::shared_ptr<sDrawData> appendDrawData(const GLchar *vao, const GLchar *shader, const GLchar *texture
      , float *vertices, GLuint verticesCount
      , GLuint *indices, GLuint indicesCount
      , GLuint count) {
    return appendDrawData(
        vaos[vao].get(), shaders[shader].get(), textures[texture].get()
      , vertices, verticesCount
      , indices, indicesCount
      , count);
  }
  std::shared_ptr<sDrawData> appendDrawData(VertexArray *vao, Shader *shader, Texture *texture
      , float *vertices, GLuint verticesCount
      , GLuint *indices, GLuint indicesCount
      , GLuint count) {
    if (!vao || !shader || !vertices || !verticesCount || !indices || !indicesCount)
      return nullptr;
    auto dd = findDrawData(vao, shader, texture);
    if (!dd) {
      auto tmp = std::make_shared<sDrawData>();
      if (!tmp) return nullptr;
      tmp->vao = vao;
      tmp->shader = shader;
      tmp->texture = texture;
      tmp->count = 0;
      draws.emplace_back(tmp);
      dd = draws.back();
      if (!dd) return nullptr;
    }
    dd->vertices.insert(dd->vertices.end(), vertices, vertices + verticesCount);
    dd->indices.insert(dd->indices.end(), indices, indices + indicesCount);
    dd->count += count;
    return dd;
  }
  std::shared_ptr<sDrawData> findDrawData(VertexArray *vao, Shader *shader, Texture *texture) {
    if (!shader || !texture) return 0;
    for (auto &e : draws) {
      if (e->vao == vao && e->shader == shader && e->texture == texture)
        return e;
    }
    return 0;
  }
  std::string findVertexArrayName(VertexArray *vao) {
    std::string d;
    auto iter = std::find_if(vaos.begin(), vaos.end()
      , [vao](const std::pair<std::string, std::shared_ptr<VertexArray>> &e) {
        return (e.second.get() == vao);
      });
    if (iter != vaos.end()) d = iter->first;
    return std::move(d);
  }
  std::string findShaderName(Shader *shader) {
    std::string d;
    auto iter = std::find_if(shaders.begin(), shaders.end()
      , [shader](const std::pair<std::string, std::shared_ptr<Shader>> &e) {
        return (e.second.get() == shader);
      });
    if (iter != shaders.end()) d = iter->first;
    return std::move(d);
  }
  std::string findTextureName(Texture *texture) {
    std::string d;
    auto iter = std::find_if(textures.begin(), textures.end()
      , [texture](const std::pair<std::string, std::shared_ptr<Texture>> &e) {
        return (e.second.get() == texture);
      });
    if (iter != textures.end()) d = iter->first;
    return std::move(d);
  }
  std::string dump(GLuint v, GLuint i) {
    std::string msg;
    msg += "---------- Dump Begin ----------\n";
    msg += "----- vertex arrays -----";
    for (auto &e : vaos) {
      msg += "\n";
      msg += e.first;
    }
    msg += "\n----- shaders -----";
    for (auto &e : shaders) {
      msg += "\n";
      msg += e.first;
    }
    msg += "\n----- textures -----";
    for (auto &e : textures) {
      msg += "\n";
      msg += e.first;
    }
    msg += "\n----- draws -----";
    for (auto &e : draws) {
      msg += "\nvao: ";
      msg += findVertexArrayName(e->vao);
      msg += "\n----------\nshader: ";
      msg += findShaderName(e->shader);
      msg += "\n----------\ntexture: ";
      msg += findTextureName(e->texture);
      msg += "\n----------\ncount:";
      msg += std::to_string(e->count);
      msg += "\n----------\nvertices:";
      for (auto it = e->vertices.begin(); it != e->vertices.end(); ++it) {
        if ((it - e->vertices.begin()) % v == 0)
          msg += "\n";
        msg += std::to_string(*it);
        msg += ",\t";
      }
      msg += "\n----------\nindices:";
      for (auto it = e->indices.begin(); it != e->indices.end(); ++it) {
        if ((it - e->indices.begin()) % v == 0)
          msg += "\n";
        msg += std::to_string(*it);
        msg += ",\t";
      }
    }
    msg += "\n---------- Dump End ----------";
    return std::move(msg);
  }
};
